package my.demo;

import java.io.IOException;

import my.demo.TSocket.Listener;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.IBinder;
import android.util.Log;

/* 
 * PushService that does all of the work.
 * Most of the logic is borrowed from KeepAliveService.
 * http://code.google.com/p/android-random/source/browse/trunk/TestKeepAlive/src/org/devtcg/demo/keepalive/KeepAliveService.java?r=219
 */
public class PushService extends Service implements Listener {
	// this is the log tag
	public static final String TAG = "DemoPushService";

	private static final String CLIENT_ID = "MY_PUSH_DEMO_TEST";

	private static final String host = "192.168.1.102";
	private static final int port = 7777;

	// These are the actions for the service (name are descriptive enough)
	private static final String ACTION_START = CLIENT_ID + ".START";
	private static final String ACTION_STOP = CLIENT_ID + ".STOP";
	private static final String ACTION_KEEPALIVE = CLIENT_ID + ".KEEP_ALIVE";
	private static final String ACTION_RECONNECT = CLIENT_ID + ".RECONNECT";

	// Connectivity manager to determining, when the phone loses connection
	private ConnectivityManager mConnMan;
	// Notification manager to displaying arrived push notifications
	private NotificationManager mNotifMan;

	// Whether or not the service has been started.
	private boolean mStarted;

	// This the application level keep-alive interval, that is used by the
	// AlarmManager
	// to keep the connection active, even when the device goes to sleep.
	private static final long KEEP_ALIVE_INTERVAL = 1000 * 60 * 3;

	// Retry intervals, when the connection is lost.
	private static final long INITIAL_RETRY_INTERVAL = 1000 * 10;
	private static final long MAXIMUM_RETRY_INTERVAL = 1000 * 60 * 30;

	// Preferences instance
	private SharedPreferences mPrefs;
	// We store in the preferences, whether or not the service has been started
	public static final String PREF_STARTED = "isStarted";
	// We store the last retry interval
	public static final String PREF_RETRY = "retryInterval";

	// Notification title
	public static String NOTIF_TITLE = "PUSH DEMO";
	// Notification id
	private static final int NOTIF_CONNECTED = 0;

	private long mStartTime;
	
	private ConnectionLog 			mLog;

	private static User _user=null;

	// Static method to start the service
	public static void actionStart(Context ctx,User user) {
		_user=user;
		Intent i = new Intent(ctx, PushService.class);
		i.setAction(ACTION_START);
		ctx.startService(i);
	}

	// Static method to stop the service
	public static void actionStop(Context ctx) {
		Intent i = new Intent(ctx, PushService.class);
		i.setAction(ACTION_STOP);
		ctx.startService(i);
	}

	// Static method to send a keep alive message
	public static void actionPing(Context ctx) {
		Intent i = new Intent(ctx, PushService.class);
		i.setAction(ACTION_KEEPALIVE);
		ctx.startService(i);
	}

	@Override
	public void onCreate() {
		super.onCreate();
		
		try {
			mLog = new ConnectionLog();
			Log.i(TAG, "Opened log at " + mLog.getPath());
		} catch (IOException e) {
			Log.e(TAG, "Failed to open log", e);
		}


		log("Creating service");
		mStartTime = System.currentTimeMillis();

		// Get instances of preferences, connectivity manager and notification
		// manager
		mPrefs = getSharedPreferences(TAG, MODE_PRIVATE);
		mConnMan = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
		mNotifMan = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

		/*
		 * If our process was reaped by the system for any reason we need to
		 * restore our state with merely a call to onCreate. We record the last
		 * "started" value and restore it here if necessary.
		 */
		handleCrashedService();
	}

	// This method does any necessary clean-up need in case the server has been
	// destroyed by the system
	// and then restarted
	private void handleCrashedService() {
		if (wasStarted() == true) {
			log("Handling crashed service...");
			// stop the keep alives
			stopKeepAlives();

			// Do a clean start
			start();
		}
	}

	@Override
	public void onDestroy() {
		log("Service destroyed (started=" + mStarted + ")");

		// Stop the services, if it has been started
		if (mStarted == true) {
			stop();
			if(mLog!=null){
				try {
					mLog.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		

	}

	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		log("Service started with intent=" + intent);

		// Do an appropriate action based on the intent.
		if (intent.getAction().equals(ACTION_STOP) == true) {
			log("intent ACTION STOP");
			stop();
			stopSelf();
		} else if (intent.getAction().equals(ACTION_START) == true) {
			log("intent ACTION START");
			start();
		} else if (intent.getAction().equals(ACTION_KEEPALIVE) == true) {
			log("intent ACTION  keepAlive");
			keepAlive();
		} else if (intent.getAction().equals(ACTION_RECONNECT) == true) {
			log("intent ACTION  ACTION_RECONNECT");
			if (isNetworkAvailable()) {
				reconnectIfNecessary();
			}
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	// log helper function
	private void log(String message) {
		log(message, null);
	}

	private void log(String message, Throwable e) {
		if (e != null) {
			Log.e(TAG, message, e);

		} else {
			Log.i(TAG, message);
		}
		
		if(mLog!=null){
			try {
				mLog.println(message);
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
	}

	// Reads whether or not the service has been started from the preferences
	private boolean wasStarted() {
		return mPrefs.getBoolean(PREF_STARTED, false);
	}

	// Sets whether or not the services has been started in the preferences.
	private void setStarted(boolean started) {
		mPrefs.edit().putBoolean(PREF_STARTED, started).commit();
		mStarted = started;
	}

	private synchronized void start() {
		log("Starting service...");

		// Do nothing, if the service is already running.
		if (mStarted == true) {
			Log.w(TAG, "Attempt to start connection that is already active");
			return;
		}

		setStarted(true);
		// Establish an connection
		connect();

		// Register a connectivity listener
		registerReceiver(mConnectivityChanged, new IntentFilter(
				ConnectivityManager.CONNECTIVITY_ACTION));
	}

	private synchronized void stop() {
		log("run method stop");
		// Do nothing, if the service is not running.
		if (mStarted == false) {
			Log.w(TAG, "Attempt to stop connection not active.");
			return;
		}

		// Save stopped state in the preferences
		setStarted(false);

		// Remove the connectivity receiver
		unregisterReceiver(mConnectivityChanged);
		// Any existing reconnect timers should be removed, since we explicitly
		// stopping the service.

		if (socket != null) {
			socket.close();
			socket = null;
		}

		cancelReconnect();
		stopKeepAlives();
	}

	public static TSocket socket;

	private synchronized void connect() {
		log("Connecting...");
		// fetch the device ID from the preferences.

		if (socket != null) {
			stopKeepAlives();
			socket.close();
			socket = null;
		}

		try {
			socket = new TSocket(host, port);
			socket.open();
			Message message = new Message();
			message.setCmd("login");
			message.setData(_user);
			socket.write(message);

			message = socket.read();

			if (message.isOk()) {
				mStartTime = System.currentTimeMillis();
				socket.startReceive();
				socket.setListener(this);
				startKeepAlives();
				cancelReconnect();
			} else {
				socket.close();
				socket = null;
				throw new Exception("login error");
			}

		} catch (Exception e) {
			// Schedule a reconnect, if we failed to connect
			log("Exception: "
					+ (e.getMessage() != null ? e.getMessage() : "NULL"));
			if (isNetworkAvailable()) {
				scheduleReconnect(mStartTime);
			}
		}finally{
			setStarted(true);
		}
	}

	private synchronized void keepAlive() {
		log("run method keepAlive");
		if (socket != null && socket.isOpen()) {
			Message msg = new Message();
			msg.setCmd("ping");
			try {
				socket.write(msg);
			} catch (IOException e) {
				stopKeepAlives();
				socket.close();
				socket = null;
				if (isNetworkAvailable()) {
					scheduleReconnect(mStartTime);
				}
			}
		}
	}

	// Schedule application level keep-alives using the AlarmManager
	private void startKeepAlives() {
		log("run method startKeepAlives");
		Intent i = new Intent();
		i.setClass(this, PushService.class);
		i.setAction(ACTION_KEEPALIVE);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
		alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP,
				System.currentTimeMillis() + KEEP_ALIVE_INTERVAL,
				KEEP_ALIVE_INTERVAL, pi);
	}

	// Remove all scheduled keep alives
	private void stopKeepAlives() {
		log("run method stopKeepAlives");
		Intent i = new Intent();
		i.setClass(this, PushService.class);
		i.setAction(ACTION_KEEPALIVE);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
		alarmMgr.cancel(pi);
	}

	// We schedule a reconnect based on the starttime of the service
	public void scheduleReconnect(long startTime) {
		if (mStarted == false) {
			return;
		}
		
		cancelReconnect();
		
		log("run method scheduleReconnect");
		// the last keep-alive interval
		long interval = mPrefs.getLong(PREF_RETRY, INITIAL_RETRY_INTERVAL);

		// Calculate the elapsed time since the start
		long now = System.currentTimeMillis();
		long elapsed = now - startTime;

		// Set an appropriate interval based on the elapsed time since start
		if (elapsed < interval) {
			interval = Math.min(interval * 4, MAXIMUM_RETRY_INTERVAL);
		} else {
			interval = INITIAL_RETRY_INTERVAL;
		}

		log("Rescheduling connection in " + interval + "ms.");

		// Save the new internval
		mPrefs.edit().putLong(PREF_RETRY, interval).commit();

		// Schedule a reconnect using the alarm manager.
		Intent i = new Intent();
		i.setClass(this, PushService.class);
		i.setAction(ACTION_RECONNECT);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
		alarmMgr.set(AlarmManager.RTC_WAKEUP, now + interval, pi);
	}

	// Remove the scheduled reconnect
	public void cancelReconnect() {
		log("run method cancelReconnect");
		Intent i = new Intent();
		i.setClass(this, PushService.class);
		i.setAction(ACTION_RECONNECT);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
		alarmMgr.cancel(pi);
	}

	private synchronized void reconnectIfNecessary() {
		log("run method reconnectIfNecessary");
		if (mStarted == true && (socket == null || !socket.isOpen())) {
			log("Reconnecting...");
			connect();
		}

	}

	// This receiver listeners for network changes and updates the MQTT
	// connection
	// accordingly
	private BroadcastReceiver mConnectivityChanged = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			// Get network info
			NetworkInfo info = (NetworkInfo) intent
					.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);

			// Is there connectivity?
			boolean hasConnectivity = (info != null && info.isConnected()) ? true
					: false;

			log("Connectivity changed: connected=" + hasConnectivity);

			if (hasConnectivity) {
				reconnectIfNecessary();
			} else if (socket != null) {
				// if there no connectivity, make sure connection is
				// destroyed
				socket.close();
				socket = null;
				stopKeepAlives();
				cancelReconnect();
			}
		}
	};

	// Display the topbar notification
	private void showNotification(String text) {
		Notification n = new Notification();

		n.flags |= Notification.FLAG_SHOW_LIGHTS;
		n.flags |= Notification.FLAG_AUTO_CANCEL;

		n.defaults = Notification.DEFAULT_ALL;

		n.icon = my.demo.R.drawable.icon;
		n.when = System.currentTimeMillis();

		// Simply open the parent activity
		PendingIntent pi = PendingIntent.getActivity(this, 0, new Intent(this,
				PushActivity.class), 0);

		// Change the name of the notification here
		n.setLatestEventInfo(this, NOTIF_TITLE, text, pi);

		mNotifMan.notify(NOTIF_CONNECTED, n);
	}

	// Check if we are online
	private boolean isNetworkAvailable() {
		NetworkInfo info = mConnMan.getActiveNetworkInfo();
		if (info == null) {
			return false;
		}
		return info.isConnected();
	}

	@Override
	public void onpush(Message msg) {
		log(  "onpush " + msg.toJson());
		showNotification(msg.toJson());
	}

	@Override
	public void onerror() {
		if (mStarted&&isNetworkAvailable()) {
			log( "onerror ");
			scheduleReconnect(mStartTime);
		}
	}

}